#include "Defines.h"
#include "AutoCalcFramework.h"
#include <time.h>
#include "PatternList.h"
#include <iostream>
#include "Config.h"
#include "Seq.h"
#include <math.h>
#include "Log.h"

using namespace std;

bool GetRefPixelArray( int x, int y, unsigned char * ref_pixel_array, unsigned char * frame, int width, int height );
unsigned char ComputeInterpolationPixel( unsigned char * ref_pixel_array, char * coff_array );

CAutoCalcFramework::CAutoCalcFramework()
{
	m_iTotalRunTimes = 0;
	m_iRunTimesForCurrentPattern = 0;
	m_timeTotalBegin = 0;
	m_timeCurrentPatternBegin = 0;

	m_pCurrentPattern = NULL;
}

bool CAutoCalcFramework::Run()
{
	if( !FirstRun() ) // Calculate the initial PSNR
	{
		return false;
	}

	double psnr = 0;
	double old_psnr = 0;

	int run_round_times = CConfig::GetConfig()->time_slice / MACRO_TIME_SLICE;

	if ( CConfig::GetConfig()->run_once )
	{
		run_round_times = 1;
	}

	for ( ; run_round_times>0; run_round_times-- )
	{
		while ( NextPattern() )
		{
			while( true )
			{

				ChangeCoffForPattern();
				m_pCurrentPattern->DoInterplate();
				psnr = CalcPSNR();
				m_pCurrentPattern->RecordPSNR( psnr );

				if ( psnr > old_psnr  )
				{
					m_pCurrentPattern->Dump();
					m_pCurrentPattern->DumpBucket();
				}

				old_psnr = (*(m_pCurrentPattern->m_pCoffBucket->m_pvecCoffList))[0]->psnr;

				if ( CheckStop() )
				{
					old_psnr = 0;
					break;
				}

			}

			// DEBUG
			// break;
		}

	}
	return true;

}

bool CAutoCalcFramework::FirstRun()
{

	
	CLog::GetInstance()->log_stream << "First Run" << endl;
	time( &m_timeTotalBegin );
	DoInterpolateAll();
	CalcPSNR();

	return true;
}

bool CAutoCalcFramework::CheckStop()
{
	CConfig * config = CConfig::GetConfig();
	time_t ptime;
	time( &ptime );

	// cout << "Check Stop ...";

	if ( config->run_once && m_iRunTimesForCurrentPattern >= 1 )
	{
		return true;
	}

	if ( ptime - m_timeCurrentPatternBegin > MACRO_TIME_SLICE )
	{
		// cout << "YES" << endl;
		return true;
	}

	// cout << "NO" << endl;
	return false;
}

bool CAutoCalcFramework::NextPattern()
{
	CPatternList * pattern_list = CPatternList::GetInstance();

	if ( m_pCurrentPattern == NULL )
	{
		m_iterCurrentPattern = pattern_list->m_pvecPattern->begin();
		m_pCurrentPattern = *m_iterCurrentPattern;
	}
	else
	{
		// Select the best coff for other patterns after
		m_pCurrentPattern->SelectBestCoff();
		m_pCurrentPattern->DoInterplate();

		// TODO: iterator sucks
		m_iterCurrentPattern++;
		if ( m_iterCurrentPattern == pattern_list->m_pvecPattern->end() )
		{
			m_pCurrentPattern = NULL;
			return false;
		}
		else
		{
			m_pCurrentPattern = *m_iterCurrentPattern;
		}

	}

	time( &m_timeCurrentPatternBegin );
	m_iRunTimesForCurrentPattern = 0;

	CLog::GetInstance()->log_stream << endl << "============= Next Pattern ============" << endl;
	return true;
}

bool CAutoCalcFramework::ChangeCoffForPattern()
{
	// cout << "Change Coff For Pattern" << endl;
	m_iRunTimesForCurrentPattern++;
	m_iTotalRunTimes++;
	m_pCurrentPattern->ChangeCoff();

	return true;
}

//bool CAutoCalcFramework::Log( string log )
//{
//	cout << log.c_str() << endl;
//	return true;
//}

double CAutoCalcFramework::CalcPSNR( bool dump_all )
{

	CLog * log = CLog::GetInstance();

	if ( dump_all ) log->log_stream << "=== Dump PSNR " << endl;

	CConfig * config = CConfig::GetConfig();

	double mse = 0;
	int diff = 0;
	unsigned char * ori_frame = NULL;
	unsigned char * int_frame = NULL;
	double psnr = 0;
	double psnr_sum = 0;

	int real_width = config->seq->m_iWidth - 2 * HORIZONTAL_SKIP_COUNT;
	int real_height = config->seq->m_iHeight - 2 * VERTICAL_SKIP_COUNT;

	for ( int i=0; i<config->encode_count; i++ )
	{
		psnr = 0;
		mse = 0;

		ori_frame = config->seq->m_ppOrigPixelArray[i];
		int_frame = config->seq->m_ppIntPixelArray[i];

		for( int y=VERTICAL_SKIP_COUNT; y<config->seq->m_iHeight-VERTICAL_SKIP_COUNT; y++ )
		{
			for( int x=HORIZONTAL_SKIP_COUNT; x<config->seq->m_iWidth-HORIZONTAL_SKIP_COUNT; x++ )
			{
				diff = ori_frame[y*config->seq->m_iWidth + x] - int_frame[y*config->seq->m_iWidth + x];
				mse += diff * diff;
			}	
		}

		mse /= (double)( real_width * real_height );

		if ( dump_all ) log->log_stream << "FRAME: " << i << " MSE:" << mse;

		psnr = ( log10( (double)(255 * 255) / mse ) ) * 10;

		if ( dump_all ) log->log_stream	 << " PSNR:" << psnr << endl;
		psnr_sum += psnr;

	}
	psnr_sum /= (double)(config->encode_count);

	if ( dump_all ) log->log_stream	<< "AVG PSNR: " << psnr_sum << endl;

	if ( dump_all ) log->log_stream << endl;
	return psnr_sum;
}

bool CAutoCalcFramework::DoInterpolateAll()
{
	CConfig * config = CConfig::GetConfig();
	CPatternList * pattern_list = CPatternList::GetInstance();
	vector<CPattern*>::iterator pattern_iter = pattern_list->m_pvecPattern->begin();
	vector<CPos>::iterator pos_iter;
	unsigned char inter_pixel = 0;
	unsigned char frame_no;
	int width = config->seq->m_iWidth;
	int height = config->seq->m_iHeight;
	int x;
	int y;
	vector<CPos> * pos_list = NULL;
	unsigned char ref_pixel_array[REF_PIXEL_NUMBER];

	// for 6tap
	unsigned char ref_pixel[6];
	char ref_coff[6] = { 1, -5, 20, 20, -5, 1 };
	char ref_x_offset[6] = { -5, -3, -1, 1, 3, 5 };
	int sum = 0;

	// patterned area
	for ( ; pattern_iter!=pattern_list->m_pvecPattern->end(); pattern_iter++ )
	{
		(*pattern_iter)->DoInterplate();
	}

	// unpatterned area
	pos_list = pattern_list->m_pvecUnpattern;
	for ( pos_iter = pos_list->begin(); pos_iter!=pos_list->end(); pos_iter++ )
	{
		frame_no = (*pos_iter).m_ucFrame;
		x = (*pos_iter).m_usX;
		y = (*pos_iter).m_usY;

		// cout << "X: " << x << " Y: " << y << endl;

		GetRefPixelArray( x, y, ref_pixel_array, config->seq->m_ppRecPixelArray[frame_no], width, height );
		inter_pixel = ComputeInterpolationPixel( ref_pixel_array, pattern_list->m_caCoff );
		config->seq->m_ppIntPixelArray[frame_no][y * width + x] = inter_pixel;

		//for ( int i=0; i<6; i++ )
		//{
		//	ref_pixel[i] = config->seq->m_ppIntPixelArray[frame_no][ y * config->seq->m_iWidth + x + ref_x_offset[i] ];
		//}
		//for ( int i=0; i<6; i++ )
		//{
		//	sum += ref_pixel[i] * ref_coff[i];
		//}
		//sum = sum >> 5;

		//if ( sum < 0 ) sum = 0;
		//if ( sum >255 ) sum = 255;

		//config->seq->m_ppIntPixelArray[frame_no][y * width + x] = sum;
	}

	return true;
}